জেনারেটর ফাংশন ব্যবহার করে অ্যাসিঙ্ক্রোনাস জাভাস্ক্রিপ্টে দক্ষতা অর্জন করুন। একাধিক জেনারেটর কম্পোজ ও সমন্বয়ের উন্নত কৌশল শিখুন, যা অ্যাসিঙ্ক্রোনাস ওয়ার্কফ্লো সহজ করে।
জাভাস্ক্রিপ্ট জেনারেটর ফাংশন অ্যাসিঙ্ক কম্পোজিশন: একাধিক জেনারেটরের সমন্বয়
জাভাস্ক্রিপ্ট জেনারেটর ফাংশনগুলো অ্যাসিঙ্ক্রোনাস অপারেশনগুলিকে আরও বেশি সিঙ্ক্রোনাস পদ্ধতিতে পরিচালনা করার জন্য একটি শক্তিশালী ব্যবস্থা প্রদান করে। যদিও জেনারেটরের সাধারণ ব্যবহার ভালোভাবে নথিভুক্ত করা আছে, তাদের আসল শক্তি নিহিত রয়েছে একাধিক অ্যাসিঙ্ক্রোনাস ডেটা স্ট্রিম নিয়ে কাজ করার সময় তাদের কম্পোজ এবং সমন্বয় করার ক্ষমতার মধ্যে। এই পোস্টে আমরা অ্যাসিঙ্ক কম্পোজিশন ব্যবহার করে একাধিক জেনারেটরের সমন্বয় সাধনের উন্নত কৌশলগুলো নিয়ে আলোচনা করব।
জেনারেটর ফাংশন বোঝা
কম্পোজিশনে যাওয়ার আগে, চলুন সংক্ষেপে জেনে নেই জেনারেটর ফাংশন কী এবং কীভাবে কাজ করে।
একটি জেনারেটর ফাংশন function* সিনট্যাক্স ব্যবহার করে ঘোষণা করা হয়। সাধারণ ফাংশনের মতো নয়, জেনারেটর ফাংশনগুলি এক্সিকিউশনের সময় পজ (pause) এবং রিজিউম (resume) করা যায়। yield কীওয়ার্ডটি ফাংশনটিকে পজ করতে এবং একটি ভ্যালু রিটার্ন করতে ব্যবহৃত হয়। যখন জেনারেটরটি রিজিউম করা হয় (next() ব্যবহার করে), তখন এক্সিকিউশন যেখান থেকে বন্ধ হয়েছিল সেখান থেকেই আবার শুরু হয়।
এখানে একটি সহজ উদাহরণ দেওয়া হলো:
function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = numberGenerator();
console.log(generator.next()); // Output: { value: 1, done: false }
console.log(generator.next()); // Output: { value: 2, done: false }
console.log(generator.next()); // Output: { value: 3, done: false }
console.log(generator.next()); // Output: { value: undefined, done: true }
অ্যাসিঙ্ক্রোনাস জেনারেটর
অ্যাসিঙ্ক্রোনাস অপারেশনগুলি পরিচালনা করতে, আমরা অ্যাসিঙ্ক্রোনাস জেনারেটর ব্যবহার করতে পারি, যা async function* সিনট্যাক্স ব্যবহার করে ঘোষণা করা হয়। এই জেনারেটরগুলি প্রমিস (promise) await করতে পারে, যা অ্যাসিঙ্ক্রোনাস কোডকে আরও লিনিয়ার এবং পঠনযোগ্য স্টাইলে লিখতে সাহায্য করে।
উদাহরণ:
async function* fetchUsers(userIds) {
for (const userId of userIds) {
const response = await fetch(`https://api.example.com/users/${userId}`);
const user = await response.json();
yield user;
}
}
async function main() {
const userIds = [1, 2, 3];
const userGenerator = fetchUsers(userIds);
for await (const user of userGenerator) {
console.log(user);
}
}
main();
এই উদাহরণে, fetchUsers একটি অ্যাসিঙ্ক্রোনাস জেনারেটর যা প্রতিটি প্রদত্ত userId-এর জন্য একটি API থেকে ব্যবহারকারীর ডেটা নিয়ে আসে। for await...of লুপটি অ্যাসিঙ্ক্রোনাস জেনারেটরের উপর ইটারেট করতে ব্যবহৃত হয়, যা প্রতিটি প্রদত্ত মান প্রসেস করার আগে তার জন্য অপেক্ষা করে।
একাধিক জেনারেটর সমন্বয়ের প্রয়োজনীয়তা
প্রায়শই, অ্যাপ্লিকেশনগুলিতে একাধিক অ্যাসিঙ্ক্রোনাস ডেটা সোর্স বা প্রসেসিং ধাপের মধ্যে সমন্বয়ের প্রয়োজন হয়। উদাহরণস্বরূপ, আপনার প্রয়োজন হতে পারে:
- একই সাথে একাধিক API থেকে ডেটা আনা।
- ডেটাকে একাধিক রূপান্তরের মধ্য দিয়ে প্রসেস করা, যেখানে প্রতিটি ধাপ একটি পৃথক জেনারেটর দ্বারা সম্পন্ন হয়।
- একাধিক অ্যাসিঙ্ক্রোনাস অপারেশনের মধ্যে ত্রুটি এবং ব্যতিক্রমগুলি পরিচালনা করা।
- জটিল কন্ট্রোল ফ্লো লজিক প্রয়োগ করা, যেমন শর্তসাপেক্ষ এক্সিকিউশন বা ফ্যান-আউট/ফ্যান-ইন প্যাটার্ন।
ঐতিহ্যবাহী অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং কৌশল, যেমন কলব্যাক বা প্রমিস, এই ধরনের পরিস্থিতিতে পরিচালনা করা কঠিন হয়ে উঠতে পারে। জেনারেটর ফাংশনগুলি আরও কাঠামোবদ্ধ এবং কম্পোজেবল একটি পদ্ধতি প্রদান করে।
একাধিক জেনারেটর সমন্বয়ের কৌশল
একাধিক জেনারেটর ফাংশন সমন্বয়ের জন্য এখানে কয়েকটি কৌশল তুলে ধরা হলো:
১. yield* ব্যবহার করে জেনারেটর কম্পোজিশন
yield* কীওয়ার্ডটি আপনাকে অন্য একটি ইটারেটর বা জেনারেটর ফাংশনের কাছে ডেডিকেট করার সুযোগ দেয়। এটি জেনারেটর কম্পোজ করার জন্য একটি মৌলিক বিল্ডিং ব্লক। এটি কার্যকরভাবে ডেডিকেটেড জেনারেটরের আউটপুটকে বর্তমান জেনারেটরের আউটপুট স্ট্রিমের মধ্যে "ফ্ল্যাটেন" করে।
উদাহরণ:
async function* generatorA() {
yield 1;
yield 2;
}
async function* generatorB() {
yield 3;
yield 4;
}
async function* combinedGenerator() {
yield* generatorA();
yield* generatorB();
}
async function main() {
for await (const value of combinedGenerator()) {
console.log(value); // Output: 1, 2, 3, 4
}
}
main();
এই উদাহরণে, combinedGenerator প্রথমে generatorA থেকে সমস্ত মান ইল্ড করে এবং তারপর generatorB থেকে সমস্ত মান ইল্ড করে। এটি একটি সরল অনুক্রমিক কম্পোজিশন।
২. Promise.all ব্যবহার করে কনকারেন্ট এক্সিকিউশন
একাধিক জেনারেটর একই সাথে চালানোর জন্য, আপনি সেগুলোকে প্রমিস-এর মধ্যে র্যাপ (wrap) করে Promise.all ব্যবহার করতে পারেন। এটি আপনাকে একাধিক উৎস থেকে সমান্তরালে ডেটা আনতে সাহায্য করে, যা পারফরম্যান্স উন্নত করে।
উদাহরণ:
async function* fetchUserData(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`);
const user = await response.json();
yield user;
}
async function* fetchPosts(userId) {
const response = await fetch(`https://api.example.com/users/${userId}/posts`);
const posts = await response.json();
for (const post of posts) {
yield post;
}
}
async function* combinedGenerator(userId) {
const userDataPromise = fetchUserData(userId).next();
const postsPromise = fetchPosts(userId).next();
const [userDataResult, postsResult] = await Promise.all([userDataPromise, postsPromise]);
if (userDataResult.value) {
yield { type: 'user', data: userDataResult.value };
}
if (postsResult.value) {
yield { type: 'posts', data: postsResult.value };
}
}
async function main() {
for await (const item of combinedGenerator(1)) {
console.log(item);
}
}
main();
এই উদাহরণে, combinedGenerator ব্যবহারকারীর ডেটা এবং পোস্টগুলি Promise.all ব্যবহার করে একই সাথে নিয়ে আসে। তারপর এটি ডেটা উৎস নির্দেশ করার জন্য একটি type প্রপার্টি সহ পৃথক অবজেক্ট হিসাবে ফলাফলগুলি ইল্ড করে।
গুরুত্বপূর্ণ বিবেচনা: for await...of দিয়ে ইটারেট করার আগে একটি জেনারেটরে .next() ব্যবহার করলে ইটারেটরটি *একবার* এগিয়ে যায়। Promise.all এর সাথে জেনারেটর ব্যবহার করার সময় এটি বোঝা অত্যন্ত গুরুত্বপূর্ণ, কারণ এটি জেনারেটরের এক্সিকিউশনটি আগে থেকেই শুরু করে দেয়।
৩. ফ্যান-আউট/ফ্যান-ইন প্যাটার্ন
ফ্যান-আউট/ফ্যান-ইন প্যাটার্ন হলো একটি সাধারণ প্যাটার্ন যা একাধিক ওয়ার্কারের মধ্যে কাজ বিতরণ এবং তারপর ফলাফল একত্রিত করার জন্য ব্যবহৃত হয়। জেনারেটর ফাংশনগুলি এই প্যাটার্নটি কার্যকরভাবে প্রয়োগ করতে ব্যবহার করা যেতে পারে।
ফ্যান-আউট: একাধিক জেনারেটরে কাজ বিতরণ করা।
ফ্যান-ইন: একাধিক জেনারেটর থেকে ফলাফল সংগ্রহ করা।
উদাহরণ:
async function* worker(taskId) {
// Simulate asynchronous work
await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
yield { taskId, result: `Result for task ${taskId}` };
}
async function* fanOut(taskIds, numWorkers) {
const workerGenerators = [];
for (let i = 0; i < numWorkers; i++) {
workerGenerators.push(worker(taskIds[i % taskIds.length])); // Round-robin assignment
}
for (let i = 0; i < taskIds.length; i++) {
yield* workerGenerators[i % numWorkers];
}
}
async function main() {
const taskIds = [1, 2, 3, 4, 5, 6, 7, 8];
const numWorkers = 3;
for await (const result of fanOut(taskIds, numWorkers)) {
console.log(result);
}
}
main();
এই উদাহরণে, fanOut একটি নির্দিষ্ট সংখ্যক ওয়ার্কারের মধ্যে কাজ (worker দ্বারা সিমুলেটেড) বিতরণ করে। রাউন্ড-রবিন অ্যাসাইনমেন্ট কাজের একটি তুলনামূলকভাবে সমান বন্টন নিশ্চিত করে। তারপর ফলাফলগুলো fanOut জেনারেটর থেকে ইল্ড (yield) করা হয়। মনে রাখবেন যে এই সহজ উদাহরণে, ওয়ার্কারগুলো সত্যি সত্যি কনকারেন্টলি চলে না; yield* fanOut এর মধ্যে সিকোয়েনশিয়াল এক্সিকিউশনকে বাধ্য করে।
৪. জেনারেটরগুলির মধ্যে মেসেজ পাসিং
জেনারেটরগুলি next() পদ্ধতির মাধ্যমে একে অপরের সাথে মান আদান-প্রদান করে যোগাযোগ করতে পারে। যখন আপনি একটি জেনারেটরে next(value) কল করেন, তখন value টি জেনারেটরের ভিতরের yield এক্সপ্রেশনে পাস করা হয়।
উদাহরণ:
async function* producer() {
let message = 'Initial Message';
while (true) {
const received = yield message;
console.log(`Producer received: ${received}`);
message = `Producer's response to: ${received}`;
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate some work
}
}
async function* consumer(producerGenerator) {
let message = 'Consumer starting';
let result = await producerGenerator.next();
console.log(`Consumer received from producer: ${result.value}`);
while (!result.done) {
const response = `Consumer's message: ${message}`; // Create a response
result = await producerGenerator.next(response); // Send message to producer
if (!result.done) {
console.log(`Consumer received from producer: ${result.value}`); // log the response from the producer
}
message = `Next consumer message`; // Create next message to send on next iteration
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate some work
}
}
async function main() {
const prod = producer();
await consumer(prod);
}
main();
এই উদাহরণে, consumer producerGenerator.next(response) ব্যবহার করে producer-কে বার্তা পাঠায়, এবং producer এই বার্তাগুলি yield এক্সপ্রেশন ব্যবহার করে গ্রহণ করে। এটি জেনারেটরগুলির মধ্যে দ্বিমুখী যোগাযোগের সুযোগ করে দেয়।
৫. ত্রুটি ব্যবস্থাপনা (Error Handling)
অ্যাসিঙ্ক্রোনাস জেনারেটর কম্পোজিশনে ত্রুটি ব্যবস্থাপনা করার জন্য সতর্কতার সাথে বিবেচনা করা প্রয়োজন। আপনি জেনারেটরের মধ্যে try...catch ব্লক ব্যবহার করে অ্যাসিঙ্ক্রোনাস অপারেশনের সময় ঘটা ত্রুটিগুলি পরিচালনা করতে পারেন।
উদাহরণ:
async function* safeFetch(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
yield data;
} catch (error) {
console.error(`Error fetching data from ${url}: ${error}`);
yield { error: error.message, url }; // Yield an error object
}
}
async function main() {
const generator = safeFetch('https://api.example.com/data'); // Replace with an actual URL, but make sure it exists to test
for await (const result of generator) {
if (result.error) {
console.log(`Failed to fetch data from ${result.url}: ${result.error}`);
} else {
console.log('Fetched data:', result);
}
}
}
main();
এই উদাহরণে, safeFetch জেনারেটরটি fetch অপারেশনের সময় ঘটা যেকোনো ত্রুটি ধরে ফেলে এবং একটি ত্রুটি অবজেক্ট ইল্ড (yield) করে। কলিং কোড তখন একটি ত্রুটির উপস্থিতি পরীক্ষা করে সেই অনুযায়ী তা পরিচালনা করতে পারে।
বাস্তব উদাহরণ এবং ব্যবহারের ক্ষেত্র
এখানে কিছু বাস্তব উদাহরণ এবং ব্যবহারের ক্ষেত্র রয়েছে যেখানে একাধিক জেনারেটরের সমন্বয় সুবিধাজনক হতে পারে:
- ডেটা স্ট্রিমিং: জেনারেটর ব্যবহার করে বড় ডেটাসেটগুলিকে খণ্ডে খণ্ডে প্রসেস করা, যেখানে একাধিক জেনারেটর একই সাথে ডেটা স্ট্রিমের উপর বিভিন্ন রূপান্তর সম্পাদন করে। ভাবুন একটি খুব বড় লগ ফাইল প্রসেস করার কথা: একটি জেনারেটর ফাইলটি পড়তে পারে, অন্যটি লাইনগুলি পার্স করতে পারে এবং তৃতীয়টি পরিসংখ্যান একত্রিত করতে পারে।
- রিয়েল-টাইম ডেটা প্রসেসিং: একাধিক উৎস থেকে রিয়েল-টাইম ডেটা স্ট্রিম, যেমন সেন্সর বা স্টক টিকার, পরিচালনা করা, যেখানে জেনারেটরগুলি ডেটা ফিল্টার, রূপান্তর এবং একত্রিত করতে ব্যবহৃত হয়।
- মাইক্রোসার্ভিস অর্কেস্ট্রেশন: জেনারেটর ব্যবহার করে একাধিক মাইক্রোসার্ভিসে কল সমন্বয় করা, যেখানে প্রতিটি জেনারেটর একটি ভিন্ন সার্ভিসে কল করার প্রতিনিধিত্ব করে। এটি একাধিক পরিষেবার মধ্যে মিথস্ক্রিয়া জড়িত জটিল ওয়ার্কফ্লোকে সহজ করতে পারে। উদাহরণস্বরূপ, একটি ই-কমার্স অর্ডার প্রসেসিং সিস্টেমে একটি পেমেন্ট পরিষেবা, একটি ইনভেন্টরি পরিষেবা এবং একটি শিপিং পরিষেবাতে কল জড়িত থাকতে পারে।
- গেম ডেভেলপমেন্ট: জেনারেটর ব্যবহার করে জটিল গেম লজিক প্রয়োগ করা, যেখানে একাধিক জেনারেটর গেমের বিভিন্ন দিক, যেমন এআই (AI), পদার্থবিজ্ঞান (physics) এবং রেন্ডারিং নিয়ন্ত্রণ করে।
- ETL (এক্সট্র্যাক্ট, ট্রান্সফর্ম, লোড) প্রসেস: জেনারেটর ফাংশন ব্যবহার করে ETL পাইপলাইনগুলিকে সুবিন্যস্ত করা, যা বিভিন্ন উৎস থেকে ডেটা এক্সট্র্যাক্ট করে, এটিকে একটি কাঙ্ক্ষিত বিন্যাসে রূপান্তর করে এবং একটি টার্গেট ডেটাবেস বা ডেটা ওয়্যারহাউসে লোড করে। প্রতিটি ধাপ (এক্সট্র্যাক্ট, ট্রান্সফর্ম, লোড) একটি পৃথক জেনারেটর হিসাবে প্রয়োগ করা যেতে পারে, যা মডুলার এবং পুনঃব্যবহারযোগ্য কোড তৈরি করতে সাহায্য করে।
অ্যাসিঙ্ক কম্পোজিশনের জন্য জেনারেটর ফাংশন ব্যবহারের সুবিধা
- উন্নত পঠনযোগ্যতা: জেনারেটর দিয়ে লেখা অ্যাসিঙ্ক্রোনাস কোড কলব্যাক বা প্রমিস দিয়ে লেখা কোডের চেয়ে বেশি পঠনযোগ্য এবং বোঝা সহজ হতে পারে।
- সরলীকৃত ত্রুটি ব্যবস্থাপনা: জেনারেটর ফাংশনগুলি
try...catchব্লক ব্যবহার করার সুযোগ দিয়ে ত্রুটি ব্যবস্থাপনা সহজ করে, যা অ্যাসিঙ্ক্রোনাস অপারেশনের সময় ঘটা ত্রুটিগুলি ধরতে পারে। - বর্ধিত কম্পোজেবিলিটি: জেনারেটর ফাংশনগুলি অত্যন্ত কম্পোজেবল, যা আপনাকে জটিল অ্যাসিঙ্ক্রোনাস ওয়ার্কফ্লো তৈরি করতে একাধিক জেনারেটরকে সহজেই একত্রিত করতে দেয়।
- উন্নত রক্ষণাবেক্ষণযোগ্যতা: জেনারেটর ফাংশনগুলির মডুলারিটি এবং কম্পোজেবিলিটি কোডকে রক্ষণাবেক্ষণ এবং আপডেট করা সহজ করে তোলে।
- উন্নত পরীক্ষাযোগ্যতা: জেনারেটর ফাংশনগুলি কলব্যাক বা প্রমিস দিয়ে লেখা কোডের চেয়ে পরীক্ষা করা সহজ, কারণ আপনি সহজেই এক্সিকিউশনের প্রবাহ নিয়ন্ত্রণ করতে এবং অ্যাসিঙ্ক্রোনাস অপারেশনগুলিকে মক করতে পারেন।
চ্যালেঞ্জ এবং বিবেচ্য বিষয়
- শেখার প্রক্রিয়া: জেনারেটর ফাংশনগুলি ঐতিহ্যবাহী অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং কৌশলগুলির চেয়ে বোঝা আরও জটিল হতে পারে।
- ডিবাগিং: অ্যাসিঙ্ক্রোনাস জেনারেটর কম্পোজিশন ডিবাগ করা চ্যালেঞ্জিং হতে পারে, কারণ এক্সিকিউশনের প্রবাহ ট্রেস করা কঠিন হতে পারে। ভালো লগিং অনুশীলন ব্যবহার করা অত্যন্ত গুরুত্বপূর্ণ।
- পারফরম্যান্স: যদিও জেনারেটরগুলি পঠনযোগ্যতার সুবিধা দেয়, ভুল ব্যবহার পারফরম্যান্সের সমস্যা তৈরি করতে পারে। বিশেষ করে পারফরম্যান্স-ক্রিটিক্যাল অ্যাপ্লিকেশনগুলিতে জেনারেটরগুলির মধ্যে কনটেক্সট স্যুইচিংয়ের ওভারহেড সম্পর্কে সচেতন থাকুন।
- ব্রাউজার সাপোর্ট: যদিও আধুনিক ব্রাউজারগুলি সাধারণত জেনারেটর ফাংশনগুলিকে ভালোভাবে সমর্থন করে, প্রয়োজনে পুরানো ব্রাউজারগুলির জন্য সামঞ্জস্যতা নিশ্চিত করুন।
- ওভারহেড: কনটেক্সট স্যুইচিংয়ের কারণে জেনারেটরগুলির ঐতিহ্যবাহী async/await এর তুলনায় সামান্য ওভারহেড রয়েছে। যদি আপনার অ্যাপ্লিকেশনে পারফরম্যান্স গুরুত্বপূর্ণ হয় তবে তা পরিমাপ করুন।
সেরা অনুশীলন (Best Practices)
- জেনারেটর ছোট এবং ফোকাসড রাখুন: প্রতিটি জেনারেটরের একটি একক, সুনির্দিষ্ট কাজ করা উচিত। এটি পঠনযোগ্যতা এবং রক্ষণাবেক্ষণযোগ্যতা উন্নত করে।
- বর্ণনামূলক নাম ব্যবহার করুন: আপনার জেনারেটর ফাংশন এবং ভেরিয়েবলের জন্য স্পষ্ট এবং বর্ণনামূলক নাম ব্যবহার করুন।
- আপনার কোড ডকুমেন্ট করুন: আপনার কোড পুঙ্খানুপুঙ্খভাবে ডকুমেন্ট করুন, প্রতিটি জেনারেটরের উদ্দেশ্য এবং এটি অন্যান্য জেনারেটরের সাথে কীভাবে ইন্টারঅ্যাক্ট করে তা ব্যাখ্যা করুন।
- আপনার কোড পরীক্ষা করুন: ইউনিট টেস্ট এবং ইন্টিগ্রেশন টেস্ট সহ আপনার কোড পুঙ্খানুপুঙ্খভাবে পরীক্ষা করুন।
- লিন্টার এবং কোড ফরম্যাটার ব্যবহার করুন: কোডের সামঞ্জস্য এবং গুণমান নিশ্চিত করতে লিন্টার এবং কোড ফরম্যাটার ব্যবহার করুন।
- লাইব্রেরি ব্যবহারের কথা ভাবুন: co বা iter-tools-এর মতো লাইব্রেরিগুলি জেনারেটরগুলির সাথে কাজ করার জন্য ইউটিলিটি সরবরাহ করে এবং সাধারণ কাজগুলিকে সহজ করতে পারে।
উপসংহার
জাভাস্ক্রিপ্ট জেনারেটর ফাংশনগুলি, যখন অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং কৌশলগুলির সাথে একত্রিত হয়, তখন জটিল অ্যাসিঙ্ক্রোনাস ওয়ার্কফ্লো পরিচালনা করার জন্য একটি শক্তিশালী এবং নমনীয় পদ্ধতি প্রদান করে। একাধিক জেনারেটর কম্পোজ এবং সমন্বয়ের কৌশলগুলিতে দক্ষতা অর্জন করে, আপনি আরও পরিষ্কার, পরিচালনাযোগ্য এবং রক্ষণাবেক্ষণযোগ্য কোড তৈরি করতে পারেন। যদিও কিছু চ্যালেঞ্জ এবং বিবেচ্য বিষয় রয়েছে, অ্যাসিঙ্ক কম্পোজিশনের জন্য জেনারেটর ফাংশন ব্যবহারের সুবিধাগুলি প্রায়শই অসুবিধার চেয়ে বেশি, বিশেষ করে জটিল অ্যাপ্লিকেশনগুলিতে যেখানে একাধিক অ্যাসিঙ্ক্রোনাস ডেটা উৎস বা প্রসেসিং ধাপের মধ্যে সমন্বয় প্রয়োজন। এই পোস্টে বর্ণিত কৌশলগুলি নিয়ে পরীক্ষা করুন এবং আপনার নিজের প্রকল্পগুলিতে একাধিক জেনারেটর সমন্বয়ের শক্তি আবিষ্কার করুন।